import axios from 'axios'
import utils from '../utils/utils'
import { constant } from '../utils/index'
import { router } from '../router/base'
import { store } from '../store/base'
import { config as mConfig } from '../utils/config'
const CancelToken = axios.CancelToken // 取消请求,执行abort方法取消请求，同时调用reject让外层的promise失败
const sources = [] // 存放每请求的source，便于取消

let statusName = 'code' // 对应后台返回结果中的code字段（Result对象)
let messageName = 'message' // 对应后台返回结果中的message字段
let successCode = 200
let needLoginCode = 401
let noAuthCode = 403
let requestHandler = null
let responseHandler = null

// axios实例
let axiosInstance = null
export default {
  install (Vue, config) {
    Vue.prototype.$request = request
    Vue.prototype.$cancel = cancel
    // 修改实例默认配置
    const {
      statusName: _statusName,
      successCode: _successCode,
      messageName: _messageName,
      noAuthCode: _noAuthCode,
      needLoginCode: _needLoginCode,
      requestHandler: _requestHandler,
      noLoginCb: _noLoginCb,
      responseHandler: _responseHandler,
      onLogout: _onLogout,
      headers,
      ...other
    } = config
    statusName = _statusName || statusName
    successCode = _successCode || successCode
    messageName = _messageName || messageName
    noAuthCode = _noAuthCode || noAuthCode
    needLoginCode = _needLoginCode || needLoginCode
    requestHandler = _requestHandler
    responseHandler = _responseHandler

    axiosInstance = axios.create({
      timeout: 45000,
      ...other,
      headers: headers || {}
    })

    // 注册请求拦截器
    axiosInstance.interceptors.request.use((config) => {
      // 给每个请求增加cancelToken
      const source = CancelToken.source()
      if (sources.length >= 20) {
        sources.shift() // 保证sources最长20
      }
      sources.push(source)
      config.cancelToken = source.token
      // 加公共请求参数
      if (requestHandler) {
        config = requestHandler(config)
      } else {
        config.headers.token = localStorage.getItem('token') || ''
      }
      return config
    }, () => {
      return Promise.reject(new Error(constant.errorCode.noResponse + '_' + '错误的请求'))
    })

    // 注册响应拦截器
    axiosInstance.interceptors.response.use((response) => {
      // sources = [] // 每次响应后，清空sources
      if (responseHandler) {
        return responseHandler({ response })
      }
      if (!utils.isObj(response.data)) {
        if (response.config.responseType === 'blob') {
          // 如果返回了异常
          if (response.data.type === 'application/json') {
            return new Promise(resolve => {
              const reader = new FileReader()
              reader.onload = e => {
                const data = JSON.parse(e.target.result)
                resolve(data[statusName] + '_' + data[messageName] || '服务器异常'
                )
              }
              reader.readAsText(response.data)
            }).then((data) => Promise.reject(data))
              .catch((error) => Promise.reject(new Error(500 + '_' + error.message || '服务器异常'
              )))
          }
          const contentDisposition = response.headers['content-disposition']
          let fileName = ''
          if (contentDisposition) {
            fileName = contentDisposition.replace(/.*filename=(.*)/, '$1')
          }
          fileName = decodeURIComponent(fileName)
          if (navigator && navigator.msSaveBlob) {
            // ie下载
            navigator.msSaveBlob(response.data, fileName)
          } else {
            const url = window.URL.createObjectURL(response.data)
            const link = document.createElement('a')
            link.style.display = 'none'
            link.href = url
            link.setAttribute('download', fileName)

            document.body.appendChild(link)
            link.click()
            URL.revokeObjectURL(link.href)
            document.body.removeChild(link)
          }
          return { data: {} }
        }
        return Promise.resolve(response.data)
      }
      if (matchCode(response.data[statusName], successCode)) {
        return Promise.resolve(response.data.result || response.data.data)
      } else {
        return Promise.reject(
          new Error(response.data[statusName] + '_' + (response.data[messageName] || '服务器异常'))
        )
      }
    }, (error) => {
      // 请求被取消
      if (axios.isCancel(error)) {
        return Promise.reject(new Error(constant.errorCode.cancel + '_' + error.message))
      }
      if (responseHandler) {
        return responseHandler({ error })
      }
      if (!error.response) {
        return Promise.reject(new Error(constant.errorCode.noResponse + '_' + '网络异常，请稍后再试'))
      } else {
        // 取响应数据里面的code，兼容有些服务器在401、403的时候error.response.status为500，而401、403存在error.response.data[statusName]
        const code = error.response.data[statusName]
        if (error.response.status === 404 || code === 404) {
          return Promise.reject(new Error(error.response.status + '_' + '无效的请求：未找到接口'))
        } else if (matchCode(error.response.status, needLoginCode) || matchCode(code, needLoginCode)) {
          utils.message.error('认证过期，请重新登录')
          if (mConfig.onLogout) {
            mConfig.onLogout({ store, router, error })
          } else {
            store.commit('logout')
            router.push('/login')
          }
          store.commit('logout')
          return Promise.reject(new Error(error.response.status + '_' + '认证过期，请重新登录'))
        } else if (matchCode(error.response.status, noAuthCode) || matchCode(code, noAuthCode)) {
          return Promise.reject(new Error(error.response.status + '_' + '没有权限'))
        }
        return Promise.reject(new Error((error.response.status || error.response.data[statusName]) + '_' + (error.response.data[messageName] || '服务器异常')
        ))
      }
    })
  }
}
function matchCode (source, target) {
  if (Array.isArray(target)) {
    return target.includes(source)
  }
  return source === target
}
/**
  * 所有请求的通用方法，此方法才会调用axios的方法
  * @param {string} method
  * @param {string} url
  * @param data  用户的请求数据，post、get一样传参就行，axios的请求拦截会分开处理
  * @param myConfig  非业务参数，主要是配置loading、error信息的显示等
  * @returns {Promise<any>}
  */
export function request (method, url, data = {}, myConfig = {}) {
  const config = {
    method, url
  }
  if (config.method === 'get') {
    config.params = data
  } else {
    config.data = data
  }
  const { loading, hideError, download, ...other } = myConfig
  if (download) {
    config.responseType = 'blob'
  }
  if (loading !== false) {
    // loading
  }
  return axiosInstance.request({ ...config, ...other })
    .then((data) => {
      if (loading !== false) {
      // hide loading
      }
      return { data }
    })
    .catch((error) => {
      // Error对象有三个属性，name错误类型,message错误描述,stack函数调用栈记录信息(错误相关信息、错误出现的位置)
      const message = error.message
      if (loading !== false) {
      // hide loading
      }
      if (message) {
        const i = message.indexOf('_')
        if (i > 0) {
          error.code = message.substring(0, i)
          error.message = message.substring(i + 1, message.length)
        }
      }
      // 默认情况下，此处统一提示服务端的错误信息，除非请求的时候设置了hideError为true
      if (!utils.isEmptyObject(error) && !hideError && ![constant.errorCode.cancel].includes(error.code)) {
        utils.message.error(error.message)
      }
      console.info('-----error : ' + JSON.stringify(error))
      return { error }
    })
}
// 取消sources里面的所有请求
export function cancel () {
  sources.forEach(source => source.cancel('请求取消'))
}
